﻿namespace Home.Security.Authentication.IdentityProviders
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Data.Entity;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using Home.Configuration;
    using Home.Http;
    using Home.Security.Authentication.Repository;

    internal class IdentityProviderWeChat : IdentityProvider
    {
        // login - authorization code

        public override Task<string> GenerateLoginUriAsync()
        {
            var redirectUri = HttpUtility.UrlEncode($"http://{ConfigurationHelper.WebsiteAddress}/login-callback");
            var state = GenerateLoginState(AuthenticationType.WeChat);
            var result = $"https://open.weixin.qq.com/connect/qrconnect?appid={ConfigurationHelper.IdentityProviderAppKeyWeChat}&redirect_uri={redirectUri}&response_type=code&scope=snsapi_login,snsapi_base,snsapi_userinfo&state={state}#wechat_redirect";
            return Task.FromResult(result);
        }

        // access token

        public override Task<string> GenerateAccessTokenRequestUriAsync(AuthorizationCodeResult authorization)
        {
            var result = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={ConfigurationHelper.IdentityProviderAppKeyWeChat}&secret={ConfigurationHelper.IdentityProviderSecretWeChat}&code={authorization.AuthorizationCode}&grant_type=authorization_code";
            return Task.FromResult(result);
        }

        public override Task<AccessTokenResult> ParseAccessTokenResponseAsync(AuthorizationCodeResult authorization, IDictionary<string, string> values)
        {
            var accessToken = values["access_token"];
            if (accessToken == null)
            {
                var errorCode = default(string);
                var errorMessage = default(string);
                GetErrorInfo(values, out errorCode, out errorMessage);
                // TODO: log error message
                return null;
            }

            var refreshToken = values["refresh_token"];
            var expiresIn = values["expires_in"];
            var openId = values["openid"];
            var scope = values["scope"];
            var unionId = values["unionid"];

            var result = new AccessTokenResult(authorization.Id, authorization.TrackId, authorization.AuthenticationType)
            {
                AccessToken = accessToken,
                RefreshToken = refreshToken,
            };
            result.Properties.Add("access_token", accessToken);
            result.Properties.Add("refresh_token", refreshToken);
            result.Properties.Add("expires_in", expiresIn);
            result.Properties.Add("openid", openId);
            result.Properties.Add("scope", scope);
            result.Properties.Add("unionid", unionId);

            return Task.FromResult(result);
        }

        // refresh token

        public override Task<string> GenerateRefreshTokenRequestUriAsync(AuthenticationEntity entity)
        {
            var result = $"https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={ConfigurationHelper.IdentityProviderAppKeyWeChat}&grant_type=refresh_token&refresh_token={entity.RefreshToken}";
            return Task.FromResult(result);
        }

        public override Task<RefreshTokenResult> ParseRefreshTokenResponseAsync(AuthenticationEntity entity, IDictionary<string, string> values)
        {
            var accessToken = values["access_token"];
            if (accessToken == null)
            {
                var errorCode = default(string);
                var errorMessage = default(string);
                GetErrorInfo(values, out errorCode, out errorMessage);
                // TODO: log error message
                return null;
            }

            var refreshToken = values["refresh_token"];
            var expiresIn = values["expires_in"];
            var openId = values["openid"];
            var scope = values["scope"];

            var result = new RefreshTokenResult(entity.Id, entity.TrackId, entity.AuthenticationType)
            {
                AccessToken = accessToken,
                RefreshToken = refreshToken,
            };
            result.Properties.Add("access_token", accessToken);
            result.Properties.Add("refresh_token", refreshToken);
            result.Properties.Add("expires_in", expiresIn);
            result.Properties.Add("openid", openId);
            result.Properties.Add("scope", scope);

            return Task.FromResult(result);
        }

        // verify access token

        public override Task<string> GenerateVerifyAccessTokenRequestUriAsync(AuthenticationEntity entity)
        {
            var openId = GetOpenId(entity);
            var result = $"https://api.weixin.qq.com/sns/auth?access_token={entity.AccessToken}&openid={openId}";
            return Task.FromResult(result);
        }

        public override Task<VerifyAccessTokenResult> ParseVerifyAccessTokenResponseAsync(AuthenticationEntity entity, IDictionary<string, string> values)
        {
            var errorCode = default(string);
            var errorMessage = default(string);
            GetErrorInfo(values, out errorCode, out errorMessage);
            var isValid = errorCode == "0" && StringComparer.OrdinalIgnoreCase.Compare("ok", errorMessage) == 0;
            var result = new VerifyAccessTokenResult(entity.Id, entity.TrackId, entity.AuthenticationType)
            {
                IsValid = isValid,
            };
            return Task.FromResult(result);
        }

        // user key

        public override Task<string> GetUserKeyAsync(AuthenticationEntity entity)
        {
            var openId = GetOpenId(entity);
            var unionId = GetUnionId(entity);
            var result = BuildUserKey(openId, unionId);
            return Task.FromResult(result);
        }

        // SNS

        public override async Task UpdateUserSnsAsync(ApplicationUserEntity entity, SnsResult sns)
        {
            var openId = sns.Properties["openid"];
            var photoUrl = sns.Properties["headimgurl"];
            var unionId = sns.Properties["unionid"];
            entity.Key = BuildUserKey(openId, unionId);
            if (string.IsNullOrWhiteSpace(photoUrl))
            {
                entity.Photo = null;
            }
            else
            {
                // TODO: notice the performance here
                entity.Photo = await GetPhotoAsync(photoUrl);
            }
        }

        public override Task<string> GenerateSnsRequestUriAsync(AuthenticationEntity entity)
        {
            var openId = GetOpenId(entity);
            var result = $"https://api.weixin.qq.com/sns/userinfo?access_token={entity.AccessToken}&openid={openId}";
            return Task.FromResult(result);
        }

        public override Task<SnsResult> ParseSnsResponseAsync(AuthenticationEntity entity, IDictionary<string, string> values)
        {
            var result = new SnsResult();
            result.Properties.TryAdd(values, "openid");
            result.Properties.TryAdd(values, "nickname");
            result.Properties.TryAdd(values, "sex");
            result.Properties.TryAdd(values, "province");
            result.Properties.TryAdd(values, "city");
            result.Properties.TryAdd(values, "country");
            result.Properties.TryAdd(values, "headimgurl");
            result.Properties.TryAdd(values, "privilege");
            result.Properties.TryAdd(values, "unionid");
            return Task.FromResult(result);
        }

        // others

        public override IDictionary<string, string> ParseHttpResponse(string content)
        {
            return ParseHttpResponseJson(content);
        }

        // helpers

        private static string GetOpenId(AuthenticationEntity entity)
        {
            var openId = default(string);
            if (entity.RefreshTokenProperties.ContainsKey("openid"))
            {
                openId = entity.RefreshTokenProperties["openid"];
            }
            else if (entity.AccessTokenProperties.ContainsKey("openid"))
            {
                openId = entity.AccessTokenProperties["openid"];
            }

            return string.IsNullOrEmpty(openId) ? null : openId;
        }

        private static string GetUnionId(AuthenticationEntity entity)
        {
            var unionId = default(string);
            if (entity.RefreshTokenProperties.ContainsKey("unionid"))
            {
                unionId = entity.RefreshTokenProperties["unionid"];
            }
            else if (entity.AccessTokenProperties.ContainsKey("unionid"))
            {
                unionId = entity.AccessTokenProperties["unionid"];
            }

            return string.IsNullOrEmpty(unionId) ? null : unionId;
        }

        private static void GetErrorInfo(IDictionary<string, string> values, out string code, out string message)
        {
            code = values["errcode"];
            message = values["errmsg"];
        }

        private static string BuildUserKey(string openId, string unionId)
        {
            return string.IsNullOrEmpty(openId) || string.IsNullOrEmpty(unionId) ? null : $"[{openId}][{unionId}]";
        }
    }
}